﻿#include <iostream>
#include <stdlib.h>
using std::cout;
using std::endl;

// 1. C/C++ 内存分布

//int globalVar = 1;
//static int staticGlobalVar = 1;
//int main()
//{
//	static int staticVar = 1;
//	int localVar = 1;
//	int num1[10] = { 1, 2, 3, 4 };
//	char char2[] = "abcd";
//	const char* pChar3 = "abcd";
//	int* ptr1 = (int*)malloc(sizeof(int) * 4);
//	int* ptr2 = (int*)calloc(4, sizeof(int));
//	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
//	free(ptr1);
//	free(ptr3);
//}

// 1. 选择题
// 选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
// globalVar在哪里？__C__   staticGlobalVar在哪里？__C__
// staticVar在哪里？__C__   localVar在哪里？__A__
// num1 在哪里？__A__
//	
// char2在哪里？__A__ * char2在哪里？__A__
// pChar3在哪里？__A__ * pChar3在哪里？__D__
// ptr1在哪里？__A__ * ptr1在哪里？__B__
// 
// 2. 填空题
// sizeof(num1) = __40__;
// sizeof(char2) = __5__;	strlen(char2) = __4__;
// sizeof(pChar3) = __4 OR 8__;	strlen(pChar3) = __4__;
// sizeof(ptr1) = __4 OR 8__;

// 有关进程地址空间的介绍: https://juejin.cn/post/7313242056741339186

//#include <stdio.h>
//#include <stdlib.h>
//
//int uninitialized_global_var;
//int initialized_global_var = 100;
//
//int main(int argc, char* argv[], char* envp[])
//{
//    printf("Code address:%p\n", main);  // 正文代码
//    printf("Initialized Data address:%p\n", &initialized_global_var);  // 已初始化数据
//    printf("Uninitialized Data address:%p\n", &uninitialized_global_var);  // 未初始化数据
//    int* p = (int*)malloc(sizeof(int));
//    printf("Heap address:%p\n", p);  //堆区
//    printf("Stack address:%p\n", &p);  // 栈区
//
//    for (int i = 0; i < argc; i++) {
//        printf("Command-line Arguments address:%p\n", argv[i]);  // 命令行参数
//    }
//
//    for (int i = 0; envp[i]; i++) {
//        printf("Environment Variables address:%p\n", envp[i]);  // 环境变量
//    }
//    return 0;
//}

// 1. 栈, 存储非静态局部变量 / 函数参数 / 返回值等, 栈是向下增长的
// 2. 内存映射段是高效的 I / O 映射方式，用于装载一个共享的动态内存库
// 用户可使用系统接口创建共享内存, 做进程间通信
// 3. 堆用于程序运行时动态内存分配, 堆是向上增长的。
// 4. 数据段--存储全局数据和静态数据.
// 5. 代码段--可执行的代码 / 只读常量

// 2. C语言中动态内存管理方式：malloc / calloc / realloc / free

//int main()
//{
//	int* p1 = (int*)malloc(sizeof(int));
//	free(p1);
//
//	int* p2 = (int*)calloc(4, sizeof(int));
//	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
//
//	// 这里需要free(p2)吗?	不需要
//	// realloc 函数会尝试调整 p2 指向的内存块的大小. 如果有足够的连续空间可以扩展原内存块
//	// realloc 会直接在原内存块的基础上进行扩展, 并返回原指针 p2
//	// 如果没有足够的连续空间, realloc 会分配一个新的大小为 sizeof(int) * 10 的内存块
//	// 将原内存块中的内容复制到新内存块中, 然后释放原内存块, 并返回指向新内存块的指针。
//	// 无论哪种情况, 最终 p3 指向的是当前有效的内存块, 而原 p2 指向的内存块要么已经被扩展, 要么已经被释放
//	// 因此, 只需要调用 free(p3) 来释放最终的内存块即可
//	free(p3);
//}

// 1. malloc/calloc/realloc的区别是什么?
// 
// malloc
// malloc 函数用于在堆上分配指定大小的内存块
// 函数原型: void* malloc(size_t size);
// 功能: 分配 size 字节的连续内存空间, 并返回一个指向该内存块起始地址的指针. 如果分配失败, 返回 NULL
// 特点: 分配的内存块中的内容是未初始化的, 包含的是随机值.
// 
// calloc
// calloc 函数也用于在堆上分配内存, 但它会将分配的内存块初始化为零
// 函数原型: void* calloc(size_t num, size_t size);
// 功能: 分配 num 个大小为 size 字节的连续内存空间, 总共分配 num * size 字节
// 并将分配的内存块中的所有字节初始化为零. 如果分配失败, 返回 NULL.
// 特点: 自动初始化内存为零, 适合用于需要初始化为零的场景, 如数组
// 
// realloc
// realloc 函数用于重新分配已经分配的内存块的大小
// 函数原型: void* realloc(void* ptr, size_t size);
// 功能: 尝试将 ptr 指向的内存块的大小调整为 size 字节
// 如果 ptr 为 NULL, 则相当于调用 malloc(size)
// 如果 size 为 0, 则相当于调用 free(ptr)
// 如果分配成功, 返回一个指向新分配内存块的指针; 如果分配失败, 返回 NULL, 原内存块不会被释放
// 特点: 可能会移动原内存块到新的位置, 因此使用 realloc 后，应该使用返回的新指针

// 3. C++ 内存管理方式
// C 语言内存管理方式在 C++ 中可以继续使用, 但有些地方就无能为力, 而且使用起来比较麻烦
// 因此 C++ 又提出了自己的内存管理方式: 通过 new 和 delete 操作符进行动态内存管理

// 3.1 new / delete 操作内置类型
//int main()
//{
//	// 动态申请一个 int 类型的空间
//	int* ptr4 = new int;
//
//	// 动态申请一个 int 类型的空间并初始化为10
//	int* ptr5 = new int(10);
//
//	// 动态申请 10 个 int 类型的空间
//	int* ptr6 = new int[10];
//
//	delete ptr4;
//	delete ptr5;
//	delete[] ptr6;
//}

// 注意: 申请和释放单个元素的空间, 使用 new 和 delete 操作符
// 申请和释放连续的空间, 使用 new[] 和 delete[]

// 3.2 new 和 delete 操作自定义类型

//struct ListNode
//{
//    int _val;
//    struct ListNode* _next;
//
//    ListNode(int x)
//        :_val(x)
//        , _next(nullptr)
//    {
//    }
//};
//
//struct ListNode* BuyListNode(int x)
//{
//    // 单纯开空间
//    struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
//    // 检查
//    newnode->_next = nullptr;
//    newnode->_val = x;
//
//    return newnode;
//}
//
//int main()
//{
//    struct ListNode* n1 = BuyListNode(1);
//    struct ListNode* n2 = BuyListNode(2);
//    struct ListNode* n3 = BuyListNode(3);
//
//    // 开空间 + 调用构造函数初始化
//    ListNode* nn1 = new ListNode(1);
//    ListNode* nn2 = new ListNode(2);
//    ListNode* nn3 = new ListNode(3);
//
//	delete nn1;
//	delete nn2;
//	delete nn3;
//
//    return 0;
//}

//class A
//{
//public:
//	A(int a = 0)
//		: _a(a)
//	{
//		cout << "A():" << this << endl;
//	}
//	~A()
//	{
//		cout << "~A():" << this << endl;
//	}
//private:
//	int _a;
//};
//
//int main()
//{
//	// new/delete 和 malloc/free 最大区别是 
//	// new/delete 对于自定义类型除了开空间还会调用构造函数和析构函数
//	A* p1 = (A*)malloc(sizeof(A));
//	A* p2 = new A(1);
//	free(p1);
//	delete p2;
//
//	// 内置类型是几乎是一样的
//	int* p3 = (int*)malloc(sizeof(int)); 
//	int* p4 = new int;
//	free(p3);
//	delete p4;
//
//	A* p5 = (A*)malloc(sizeof(A) * 10);
//	A* p6 = new A[10]{ 1, 2, 3 };	// 剩余的未初始化的数组元素值为0
//	free(p5);
//	delete[] p6;
//
//	return 0;
//}

// 4. operator new 与 operator delete 函数
// 4.1 operator new 与 operator delete 函数
// new 和 delete 是用户进行动态内存申请和释放的操作符,
// operator new 和 operator delete 是系统提供的全局函数,
// new 在底层调用 operator new 全局函数来申请空间
// delete 在底层通过 operator delete 全局函数来释放空间

// operator new: 该函数实际通过 malloc 来申请空间
// 当 malloc 申请空间成功时直接返回
// 申请空间失败, 尝试执行空间不足应对措施
// 如果用户提供该措施, 则继续申请, 否则抛异常(bad_alloc 类型异常)

// operator delete: 该函数最终是通过 free 来释放空间的

// 5. new 和 delete 的实现原理
// 5.1 内置类型
// 如果申请的是内置类型的空间, new 和 malloc, delete 和 free 基本类似, 不同的地方是:
// new / delete 申请和释放的是单个元素的空间, new[] 和 delete[] 申请的是连续空间
// 而且 new 在申请空间失败时会抛异常, malloc 会返回 NULL
// 
// 5.2 自定义类型
// new 的原理
// 1. 调用 operator new 函数申请空间
// 2. 在申请的空间上执行构造函数, 完成对象的构造
// delete 的原理
// 1. 在空间上执行析构函数, 完成对象中资源的清理工作
// 2. 调用 operator delete 函数释放对象的空间
// 
// new T[N]的原理
// 1. 调用 operator new[]函数, 在 operator new[]中实际调用 
// operator new 函数完成 N 个对象空间的申请
// 2. 在申请的空间上执行 N 次构造函数
// delete[]的原理
// 1. 在释放的对象空间上执行 N 次析构函数, 完成 N 个对象中资源的清理
// 2. 调用 operator delete[]释放空间, 实际在 operator delete[]中调用
// operator delete来释放空间

// 6. 定位 new 表达式 (placement - new)
// 定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
// 使用格式:
// new (place_address) type 或者 new(place_address) type(initializer - list)
// place_address 必须是一个指针, initializer - list 是类型的初始化列表
// 使用场景:
// 定位 new 表达式在实际中一般是配合内存池使用. 因为内存池分配出的内存没有初始化, 
// 所以如果是自定义类型的对象, 需要使用 new 的定义表达式进行显示调构造函数进行初始化.

//class A
//{
//public:
//	A(int a = 0)
//		: _a(a)
//	{
//		cout << "A():" << this << endl;
//	}
//	~A()
//	{
//		cout << "~A():" << this << endl;
//	}
//private:
//	int _a;
//};
//
//int main()
//{
//	// p1 现在指向的只不过是与 A 对象相同大小的一段空间
//	// 还不能算是一个对象, 因为没有执行构造函数
//	A* p1 = (A*)malloc(sizeof(A));
//	new(p1)A;	
//	p1->~A();
//	free(p1);
//
//	A* p2 = (A*)operator new(sizeof(A));
//	new(p2)A(10);
//	p2->~A();
//	operator delete(p2);
//	return 0;
//}

// 7. 常见面试题
// 7.1 malloc / free 和 new / delete 的区别
// malloc / free 和 new / delete 的共同点是:
// 都是从堆上申请空间, 并且需要用户手动释放, 不同的地方是:
// 1. malloc 和 free是函数, new 和 delete 是操作符
// 2. malloc 申请的空间不会初始化, new 可以初始化
// 3. malloc 申请空间时, 需要手动计算空间大小并传递
// new 只需在其后跟上对象的类型即可.
// 如果是多个对象, []中指定对象个数即可
// 4. malloc 的返回值为 void*, 在使用时必须强转, 
// new不需要, 因为 new 后跟的是实体的类型
// 5. malloc 申请空间失败时, 返回的是NULL, 因此使用时必须判空
// 而 new 不需要, 但是 new 需要捕获异常 (catch exception)
// 6. 申请自定义类型对象时, malloc / free 只会开辟空间, 不会调用构造函数与析构函数
// 而 new 在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成空间中资源的清理

// 7.2 内存泄漏(略)